/*
 * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <debug.h>
#include <delay_timer.h>
#include <errno.h>
#include <hi3660.h>
#include <mmio.h>

#include "hikey960_private.h"

#define ADC_ADCIN0                0
#define ADC_ADCIN1                1
#define ADC_ADCIN2                2

#define HKADC_DATA_GRADE0            0
#define HKADC_DATA_GRADE1            100
#define HKADC_DATA_GRADE2            300
#define HKADC_DATA_GRADE3            500
#define HKADC_DATA_GRADE4            700
#define HKADC_DATA_GRADE5            900
#define HKADC_DATA_GRADE6            1100
#define HKADC_DATA_GRADE7            1300
#define HKADC_DATA_GRADE8            1500
#define HKADC_DATA_GRADE9            1700
#define HKADC_DATA_GRADE10            1800

#define BOARDID_VALUE0                0
#define BOARDID_VALUE1                1
#define BOARDID_VALUE2                2
#define BOARDID_VALUE3                3
#define BOARDID_VALUE4                4
#define BOARDID_VALUE5                5
#define BOARDID_VALUE6                6
#define BOARDID_VALUE7                7
#define BOARDID_VALUE8                8
#define BOARDID_VALUE9                9
#define BOARDID_UNKNOWN                0xF

#define BOARDID3_BASE                5


static void init_adc(void)
{
    /* reset hkadc */
    mmio_write_32(CRG_PERRSTEN2_REG, PERRSTEN2_HKADCSSI);
    /* wait a few clock cycles */
    udelay(2);
    mmio_write_32(CRG_PERRSTDIS2_REG, PERRSTEN2_HKADCSSI);
    udelay(2);
    /* enable hkadc clock */
    mmio_write_32(CRG_PERDIS2_REG, PEREN2_HKADCSSI);
    udelay(2);
    mmio_write_32(CRG_PEREN2_REG, PEREN2_HKADCSSI);
    udelay(2);
}

static int get_adc(unsigned int channel, unsigned int *value)
{
    unsigned int    data, value1, value0;

    if (channel > HKADC_CHANNEL_MAX) {
        WARN("invalid channel:%d\n", channel);
        return -EFAULT;
    }
    /* configure the read/write operation for external HKADC */
    mmio_write_32(HKADC_WR01_DATA_REG, HKADC_WR01_VALUE | channel);
    mmio_write_32(HKADC_WR23_DATA_REG, HKADC_WR23_VALUE);
    mmio_write_32(HKADC_WR45_DATA_REG, HKADC_WR45_VALUE);
    /* configure the number of accessing registers */
    mmio_write_32(HKADC_WR_NUM_REG, HKADC_WR_NUM_VALUE);
    /* configure delay of accessing registers */
    mmio_write_32(HKADC_DELAY01_REG, HKADC_CHANNEL0_DELAY01_VALUE);
    mmio_write_32(HKADC_DELAY23_REG, HKADC_DELAY23_VALUE);

    /* start HKADC */
    mmio_write_32(HKADC_DSP_START_REG, 1);
    do {
        data = mmio_read_32(HKADC_DSP_START_REG);
    } while (data & 1);

    /* convert AD result */
    value1 = mmio_read_32(HKADC_DSP_RD2_DATA_REG) & 0xffff;
    value0 = mmio_read_32(HKADC_DSP_RD3_DATA_REG) & 0xffff;

    data = ((value1 << 4) & HKADC_VALUE_HIGH) |
           ((value0 >> 4) & HKADC_VALUE_LOW);
    *value = data;
    return 0;
}

static int get_value(unsigned int channel, unsigned int *value)
{
    int ret;

    ret = get_adc(channel, value);
    if (ret)
        return ret;

    /* convert ADC value to micro-volt */
    ret = ((*value & HKADC_VALID_VALUE) * HKADC_VREF_1V8) / HKADC_ACCURACY;
    *value = ret;
    return 0;
}

static int adcin_data_remap(unsigned int adcin_value)
{
    int    ret;

    if (adcin_value < HKADC_DATA_GRADE1)
        ret = BOARDID_VALUE0;
    else if (adcin_value < HKADC_DATA_GRADE2)
        ret = BOARDID_VALUE1;
    else if (adcin_value < HKADC_DATA_GRADE3)
        ret = BOARDID_VALUE2;
    else if (adcin_value < HKADC_DATA_GRADE4)
        ret = BOARDID_VALUE3;
    else if (adcin_value < HKADC_DATA_GRADE5)
        ret = BOARDID_VALUE4;
    else if (adcin_value < HKADC_DATA_GRADE6)
        ret = BOARDID_VALUE5;
    else if (adcin_value < HKADC_DATA_GRADE7)
        ret = BOARDID_VALUE6;
    else if (adcin_value < HKADC_DATA_GRADE8)
        ret = BOARDID_VALUE7;
    else if (adcin_value < HKADC_DATA_GRADE9)
        ret = BOARDID_VALUE8;
    else if (adcin_value < HKADC_DATA_GRADE10)
        ret = BOARDID_VALUE9;
    else
        ret = BOARDID_UNKNOWN;
    return ret;
}

int hikey960_read_boardid(unsigned int *id)
{
    unsigned int    adcin0, adcin1, adcin2;
    unsigned int    adcin0_remap, adcin1_remap, adcin2_remap;

    assert(id != NULL);

    init_adc();

    /* read ADC channel0 data */
    get_value(ADC_ADCIN0, &adcin0);
    adcin0_remap = adcin_data_remap(adcin0);
    INFO("[BDID]adcin0:%d adcin0_remap:%d\n", adcin0, adcin0_remap);
    if (adcin0_remap == BOARDID_UNKNOWN)
        return -EINVAL;
    /* read ADC channel1 data */
    get_value(ADC_ADCIN1, &adcin1);
    adcin1_remap = adcin_data_remap(adcin1);
    INFO("[BDID]adcin1:%d adcin1_remap:%d\n", adcin1, adcin1_remap);
    if (adcin1_remap == BOARDID_UNKNOWN)
        return -EINVAL;
    /* read ADC channel2 data */
    get_value(ADC_ADCIN2, &adcin2);
    adcin2_remap = adcin_data_remap(adcin2);
    INFO("[BDID]adcin2:%d adcin2_remap:%d\n", adcin2, adcin2_remap);
    if (adcin2_remap == BOARDID_UNKNOWN)
        return -EINVAL;
    *id = BOARDID3_BASE * 1000 + (adcin2_remap * 100) +
        (adcin1_remap * 10) + adcin0_remap;
    INFO("[BDID]boardid: %d\n", *id);
    return 0;
}
